package com.jintian.smart.kernel.common.factory;

import org.apache.commons.beanutils.PropertyUtils;

import javax.management.*;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class DynamicMBeanFactory {
	public static DynamicMBean createDynamicMBean(Object object, Method... operations) {
		return new DynamicMBeanImpl(object, operations);
	}

	public static class DynamicMBeanImpl implements DynamicMBean {

		private Object object;

		/**
		 * 描述属性信息
		 */
		private List<MBeanAttributeInfo> attributes = new ArrayList<MBeanAttributeInfo>();
		/**
		 * MBeanInfo用于管理以上描述信息
		 */
		private MBeanInfo mBeanInfo;

		private DynamicMBeanImpl(Object object, Method... operations) {
			this.object = object;
			buildDynamicInfo(object.getClass(), operations);
		}

		private void buildDynamicInfo(Class<?> clazz, Method[] operations) {
			try {
				PropertyDescriptor[] descs = PropertyUtils.getPropertyDescriptors(clazz);
				for (PropertyDescriptor desc : descs) {
					if (desc.getName().equals("class") || desc.getReadMethod() == null && desc.getWriteMethod() == null)
						continue;
					attributes.add(new MBeanAttributeInfo(desc.getName(), desc.getPropertyType().getName(),
							desc.getDisplayName(), desc.getReadMethod() != null, desc.getWriteMethod() != null,
							desc.getReadMethod() != null && desc.getReadMethod().getName().startsWith("is")));
				}
				MBeanOperationInfo[] operationInfos = new MBeanOperationInfo[operations.length];
				int n = 0;
				for (Method operation : operations) {
					operationInfos[n++] = new MBeanOperationInfo(operation.getName(), operation);
				}
				mBeanInfo = new MBeanInfo(clazz.getName(), clazz.getSimpleName(),
						attributes.toArray(new MBeanAttributeInfo[attributes.size()]), new MBeanConstructorInfo[0],
						operationInfos, new MBeanNotificationInfo[0]);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		@Override
		public Object getAttribute(String attribute)
				throws AttributeNotFoundException, MBeanException, ReflectionException {
			try {
				return PropertyUtils.getProperty(this.object, attribute);
			} catch (IllegalAccessException | InvocationTargetException e) {
				throw new ReflectionException(e);
			} catch (NoSuchMethodException e) {
				throw new AttributeNotFoundException(e.getMessage());
			}
		}

		@Override
		public void setAttribute(Attribute attribute)
				throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
			try {
				PropertyUtils.setProperty(this.object, attribute.getName(), attribute.getValue());
			} catch (IllegalAccessException | InvocationTargetException e) {
				throw new ReflectionException(e);
			} catch (NoSuchMethodException e) {
				throw new AttributeNotFoundException(e.getMessage());
			}
		}

		@Override
		public AttributeList getAttributes(String[] attributes) {
			if (attributes == null || attributes.length == 0) {
				return null;
			}
			try {
				AttributeList attrList = new AttributeList();
				for (String attrName : attributes) {
					Object obj = this.getAttribute(attrName);
					Attribute attribute = new Attribute(attrName, obj);
					attrList.add(attribute);
				}
				return attrList;
			} catch (Exception e) {
				e.printStackTrace();
			}
			return null;
		}

		@Override
		public AttributeList setAttributes(AttributeList attributes) {
			if (attributes == null || attributes.isEmpty()) {
				return attributes;
			}
			try {
				for (Iterator<Object> it = attributes.iterator(); it.hasNext();) {
					Attribute attribute = (Attribute) it.next();
					setAttribute(attribute);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			return attributes;
		}

		@Override
		public Object invoke(String actionName, Object[] params, String[] signature)
				throws MBeanException, ReflectionException {
			Method methods[] = this.object.getClass().getMethods();
			for (Method method : methods) {
				String name = method.getName();
				if (name.equals(actionName)) {
					try {
						return method.invoke(object, params);
					} catch (Exception e) {
						throw new ReflectionException(e);
					}
				}
			}
			return new MBeanException(new NullPointerException("not exists actionName " + actionName));
		}

		@Override
		public MBeanInfo getMBeanInfo() {
			return this.mBeanInfo;
		}

	}
}
